#include "Common.h"

MapHandler::MapHandler(WorldMapInfo* inf, ui32 mapid)
{
	m_mapinfo = inf;
	m_mapid = mapid;
	m_crguids = 0;
	display = false;
	cells = new MapCell**[200];
	for(ui32 i = 0; i < 200; ++i)
	{
		cells[i] = new MapCell*[200];
		for(ui32 a = 0; a < 200; ++a)
		{
			cells[i][a] = new MapCell;
			cells[i][a]->m_pBelonging = this;
			cells[i][a]->xid = i;
			cells[i][a]->yid = a;
		}
	}

	m_mapinterface = new MapInterface(this);
}

void MapHandler::TryReanimate()
{
	Log->Error("MapHandler", "Map %u caught an severe error and is trying to reanimate...", m_mapid);
#ifdef WIN32
	DWORD caller = GetCurrentThreadId();
	if(caller != m_pid)
		return;
#endif
	try
	{
		{
			vector<EventHolder*> tmp;
			tmp.resize(m_activeholders.size());
			copy(m_activeholders.begin(), m_activeholders.end(), tmp.begin());
			vector<EventHolder*>::iterator itr = tmp.begin();
			vector<EventHolder*>::iterator end = tmp.end();

			for( ; itr != end; ++itr)
			{
				(*itr)->ForceRegister(this);
			}
			tmp.clear();
		}
		unordered_set<Object*> m_tmpobjects;
		for(int i = 0; i < 200; ++i)
		{
			for(int j = 0; j < 200; ++j)
			{
				MapCell* cell = cells[i][j];
				if(!cell)
					continue;
				set<Object*> tmpset;
				std::copy(cell->m_objects.begin(), cell->m_objects.end(), std::inserter(tmpset, tmpset.begin()));
				set<Object*>::iterator itr = tmpset.begin();
				set<Object*>::iterator end = tmpset.end();

				for( ; itr != tmpset.end(); ++itr)
				{
					Object* obj = (*itr);
					if(!obj)
						continue;
					try{
						obj->GetUI32Value(OBJECT_FIELD_ENTRY);
					}
					catch(...)
					{
						continue;
					}
					m_tmpobjects.insert(obj);
					RemoveObject(obj);
				}
				tmpset.clear();
				cell->m_objects.clear();
			}
		}
		unordered_set<Object*>::iterator itr = m_tmpobjects.begin();
		unordered_set<Object*>::iterator end = m_tmpobjects.end();

		for( ; itr != end; ++itr)
			AddObject(*itr);
	}
	catch(...)
	{
	}
}

void MapHandler::Loop()
{
#ifdef WIN32
	m_pid = GetCurrentThreadId();
#endif
	while(ThreadState)
	{
		ProcessUpdates();
		Sleep(100);
	}
}

void MapHandler::ProcessUpdates()
{
	WorkHolders();
	if(!m_updateobjects.size() && !m_insertobjects.size())
		return;

	set<Object*>::iterator insit = m_insertobjects.begin();
	set<Object*>::iterator insen = m_insertobjects.end();

	for( ; insit != insen && m_insertobjects.size(); ++insit)
	{
		if((*insit)->GetTypeId() == TYPEID_PLAYER)
		{
			RealmPacket update(0, 0);
			(*insit)->BuildCreation((Player*)(*insit), &update);
			((Player*)(*insit))->PushCreationData(&update);
			((Player*)(*insit))->m_timeresync = true;
			((Player*)(*insit))->ProcessUpdates();
			((Player*)(*insit))->LoadItems();
		}
		if((*insit)->GetTypeId() != TYPEID_ITEM)
		{
			float x = (*insit)->GetPosition()._x;
			float y = (*insit)->GetPosition()._y;
			ui32 a = x > 0 ? ui32(100 + (x / 1000.0f)) : ui32(100 - (abs(x) / 1000.0f));
			ui32 b = y > 0 ? ui32(100 + (y / 1000.0f)) : ui32(100 - (abs(x) / 1000.0f));
			MapCell* cell = cells[a][b];
			(*insit)->m_mapcell = cell; // We put the cell anyway, even if it points to 0. We check it in every function
			if(!cell)
				continue;
			cell->m_objects.insert((*insit));
			SearchEnvironment((*insit), a, b);
		}
		(*insit)->OnPushToWorld();
	}
	m_insertobjects.clear();
	ScopedLock<Mutex> lock(m_objectlock);
	set<Object*>::iterator itr = m_updateobjects.begin();
	set<Object*>::iterator end = m_updateobjects.end();
	for( ; itr != m_updateobjects.end(); ++itr)
	{
		if((*itr)->m_moved)
		{
			UpdateSet(*itr);
		}
		if((*itr)->m_update)
		{
			RealmPacket update(0, 0);
			(*itr)->BuildFieldUpdates(&update);
			(*itr)->UpdateInRangeSet(&update, false);
			if((*itr)->GetTypeId() == TYPEID_PLAYER)
				((Player*)(*itr))->PushUpdateData(&update);
			else
				UpdateSet(*itr);
			(*itr)->m_update = false;
		}
	}
	m_updateobjects.clear();
}

void MapHandler::WorkHolders()
{
	lock.Acquire();
	if(!m_activeholders.size())
	{
		lock.Release();
		return;
	}
	set<EventHolder*>::iterator itr = m_activeholders.begin();
	ui32 ticks = GetTickCount();
	if(!m_lastobjectupdate)
		m_lastobjectupdate = ticks ;

	ui32 diff = ticks - m_lastobjectupdate;
	m_lastobjectupdate = ticks ;

	for( ; itr != m_activeholders.end() && m_activeholders.size(); ++itr)
	{
		if(!(*itr))
		{
			m_activeholders.erase(itr);
			if(itr == m_activeholders.end())
				break;;
			continue;
		}
		if(&itr == 0)
			break;
		bool active = (*itr)->Update(diff);
		if(!active)
		{
			m_activeholders.erase(itr);
			if(itr == m_activeholders.end())
				break;;
		}
	}
	lock.Release();
}

void MapHandler::UpdateSet(Object* obj)
{
	MapCell* cell = obj->m_mapcell;
	if(!cell)
		return;
	int a = cell->xid, b = cell->yid;
	set<Object*> outranged;
	if(obj->m_inrangeset.size())
	{
		set<Object*>::iterator itr = obj->m_inrangeset.begin();
		for( ; itr != obj->m_inrangeset.end(); ++itr)
		{
			// We cant see peoplez on other maps (in fact we could but they would be displayed on our map)
			if((*itr)->GetPosition()._mapid != m_mapid)
			{
				outranged.insert(*itr);
				continue;
			}

			// Same map, so calculate distance to target
			float distance = Useable::GetDistance(*itr, obj);
			if(distance > NORMAL_DISTANCE)
			{
				// Non-static gameobjects get pushed out at normale distance
				if((*itr)->GetTypeId() == TYPEID_GAMEOBJECT && !((GameObject*)(*itr))->IsStatic())
					outranged.insert(*itr);
				// Static gameobjects rest 500 feet (maybe ill increase that a bit)
				else if((*itr)->GetTypeId() == TYPEID_GAMEOBJECT && ((GameObject*)(*itr))->IsStatic() && distance > 500.0f)
					outranged.insert(*itr);
				// Other objects get pushed out at normal distance
				else if((*itr)->GetTypeId() != TYPEID_GAMEOBJECT)
					outranged.insert(*itr);
			}
		}
	}
	set<Object*>::iterator itr = outranged.begin();
	set<Object*>::iterator end = outranged.end();
	for( ; itr != outranged.end(); ++itr)
		obj->PushOutOfRange(*itr);
	outranged.clear();
	// Refill our inrangeset
	SearchEnvironment(obj, a, b);
	/*cell->FindInRange(obj);
	cell = cells[a + 1][b];
	cell->FindInRange(obj);
	cell = cells[a][b + 1];
	cell->FindInRange(obj);
	cell = cells[a + 1][b + 1];
	cell->FindInRange(obj);
	if(a > 0)
	{
		cell = cells[a - 1][b];
		cell->FindInRange(obj);
		cell = cells[a - 1][b + 1];
		cell->FindInRange(obj);
	}
	if(b > 0)
	{
		cell = cells[a][b - 1];
		cell->FindInRange(obj);
		cell = cells[a + 1][b - 1];
		cell->FindInRange(obj);
	}
	if(a > 0 && b > 0)
	{
		cell = cells[a - 1][b - 1];
		cell->FindInRange(obj);
	}*/
}

void MapHandler::AddUpdateObject(Object* obj)
{
	if(obj && m_updateobjects.find(obj) == m_updateobjects.end())
		m_updateobjects.insert(obj);
}

ui8 MapHandler::CheckAdd(Object* obj, void* misc)
{
	if(!m_mapinfo)
		return MAP_ERROR_NOT_ACTIVE;
	if(obj->GetUI32Value(UNIT_FIELD_LEVEL) < m_mapinfo->minlevel)
	{
		ui16* b = (ui16*)misc;
		b = new ui16;
		*(ui16*)misc = m_mapinfo->minlevel;
		return MAP_ERROR_TO_LOW;
	}
	return MAP_ERROR_NONE;
}

void MapHandler::RemoveObject(Object* obj)
{
	ScopedLock<Mutex> lock(m_objectlock);
	if(!obj)
		return;
	if(!obj->m_inrangeset.size())
		return;
	set<Object*>::iterator itr = obj->m_inrangeset.begin();
	set<Object*>::iterator end = obj->m_inrangeset.end();
	for( ; itr != obj->m_inrangeset.end(); ++itr)
	{
		(*itr)->PushOutOfRange(obj);
		if((*itr)->GetTypeId() == TYPEID_PLAYER)
		{
			((Player*)(*itr))->ProcessUpdates();
		}
	}
	obj->m_mapcell->m_objects.erase(obj);
	obj->m_mapcell = NULL;
	obj->ClearAllInrange();
}

void MapHandler::AddObject(Object* obj)
{
	if(obj)
		m_insertobjects.insert(obj);
}

Creature* MapHandler::CreateCreature(ui32 entry)
{
	ui64 guid = (ui64)0xF1300000 << 32;
	char* gid = (char*)&guid;
	char* ent = (char*)&entry;
	gid[3] = ent[0];
	gid[4] = ent[1];
	gid[5] = ent[2];
	gid[6] = ent[3];
	++m_crguids;
	guid |= m_crguids;
	return new Creature(guid);
}

GameObject* MapHandler::CreateGameObject(ui32 entry)
{
	ui64 highguid = (ui64)HIGHGUID_TYPE_GAMEOBJECT << 32;
	return new GameObject( highguid | m_objguids++);
}

Creature* MapHandler::GetCreature(ui64 guid)
{
	/*if(Useable::GetTypeFromGuid(guid) != HIGHGUID_TYPE_UNIT)
		return 0;*/
	for(int i = 0; i < 200; ++i)
	{
		for(int j = 0; j < 200; ++j)
		{
			MapCell* cell = cells[i][j];
			if(!cell)
				continue;
			set<Object*>::iterator itr = cell->m_objects.begin();
			set<Object*>::iterator end = cell->m_objects.end();

			for( ; itr != cell->m_objects.end(); ++itr)
			{
				if((*itr)->GetGUID() == guid && (*itr)->GetTypeId() == TYPEID_UNIT)
					return (Creature*)(*itr);
			}
		}
	}
	return NULL;
}

GameObject* MapHandler::GetGameObject(ui64 guid)
{
	/*if(Useable::GetTypeFromGuid(guid) != HIGHGUID_TYPE_UNIT)
		return 0;*/
	for(int i = 0; i < 200; ++i)
	{
		for(int j = 0; j < 200; ++j)
		{
			MapCell* cell = cells[i][j];
			if(!cell)
				continue;
			set<Object*>::iterator itr = cell->m_objects.begin();
			set<Object*>::iterator end = cell->m_objects.end();

			for( ; itr != cell->m_objects.end(); ++itr)
			{
				if((*itr)->GetGUID() == guid && (*itr)->GetTypeId() == TYPEID_GAMEOBJECT)
					return (GameObject*)(*itr);
			}
		}
	}
	return NULL;
}

void MapHandler::UpdateCellObjects()
{
	for(int i = 0; i < 200; ++i)
	{
		for(int j = 0; j < 200; ++j)
		{
			MapCell* cell = cells[i][j];
			if(!cell)
				continue;
			set<Object*>::iterator itr = cell->m_objects.begin();
			set<Object*>::iterator end = cell->m_objects.end();

			for( ; itr != cell->m_objects.end(); ++itr)
			{
				(*itr)->Update(0);
			}
		}
	}
}

void MapHandler::LoadCreatures()
{
	return;
	QResult* res = CharacterDatabase.SafeQuery("Select * from creature_spawns where mapid = %u", m_mapid);
	if(!res || !res->NumRows())
		return;
	ui32 crs = 0;
	do
	{
		QField* rw = res->Fetch();
		ui32 entry = rw[1].GetUI32();
		Creature* cr = CreateCreature(entry);
		if(!cr)
			continue;
		cr->Load(entry);
		cr->m_maphandler = this;
		cr->m_sqlid = rw[0];
		ui32 main, off, range;
		main = off = range = 0;
		if(rw[7].GetChar())
			main = atoi(rw[7]);
		if(rw[8].GetChar())
			off = atoi(rw[8]);
		if(rw[9].GetChar())
			range = atoi(rw[9]);
		if(main)
			cr->SetUI32Value(UNIT_VIRTUAL_ITEM_SLOT_ID, main);
		if(off)
			cr->SetUI32Value(UNIT_VIRTUAL_ITEM_SLOT_ID_1, off);
		if(range)
			cr->SetUI32Value(UNIT_VIRTUAL_ITEM_SLOT_ID_2, range);
		cr->Spawn(rw[2].GetFloat(), rw[3].GetFloat(), rw[4].GetFloat(), m_mapid, rw[5].GetFloat());
		ui32 emote = rw[10];
		if(emote)
			cr->SetUI32Value(UNIT_NPC_EMOTESTATE, emote);
		++crs;
	}
	while(res->NextRow());
	delete res;
	Log->Notice("MapHandler", "Spawned %u creatures on (%s)", crs, m_mapinfo->name);
}

void MapHandler::LoadGameObjects()
{
	QResult* res = CharacterDatabase.SafeQuery("Select * from gameobject_spawns where mapid = %u", m_mapid);
	if(!res || !res->NumRows())
	{
		Log->Notice("MapHandler", "Spawned 0 GOs on (%s)", m_mapinfo->name);
		return;
	}
	ui32 crs = 0;
	do
	{
		QField* rw = res->Fetch();
		ui32 entry = rw[1];
		GameObject* go = CreateGameObject(entry);
		if(!go)
			continue;
		go->Load(entry);
		go->m_maphandler = this;
		bool static_ = rw[7].GetUI32();
		go->Spawn(rw[2].GetFloat(), rw[3].GetFloat(), rw[4].GetFloat(), m_mapid, rw[6].GetFloat(), static_);
		if(static_)
			m_staticgos.insert(go);
		++crs;
	}
	while(res->NextRow());
	delete res;
	Log->Notice("MapHandler", "Spawned %u GOs on (%s)", crs, m_mapinfo->name);
}

void MapCell::FindInRange(Object* obj)
{
	if(!m_objects.size())
		return;
	set<Object*>::iterator itr = m_objects.begin();
	set<Object*>::iterator end = m_objects.end();

	for( ; itr != m_objects.end(); ++itr)
	{
		if(obj && *itr && Useable::GetDistance(obj, *itr) < NORMAL_DISTANCE && *itr != obj)
		{
			obj->AddInRangeObject(*itr);
			(*itr)->AddInRangeObject(obj);
		}
	}
}

void MapHandler::UpdateMapCell(Object* obj)
{
	float x = obj->GetPosition()._x;
	float y = obj->GetPosition()._y;
	ui32 a = x > 0 ? ui32(100 + (x / 1000.0f)) : ui32(100 - (abs(x) / 1000.0f));
	ui32 b = y > 0 ? ui32(100 + (y / 1000.0f)) : ui32(100 - (abs(x) / 1000.0f));
	if(a >= 200 || b >= 200)
		return;
	MapCell* cell = cells[a][b];
	if(cell && cell != obj->m_mapcell)
	{
		if(obj->m_mapcell)
			obj->m_mapcell->m_objects.erase(obj);
		cell->m_objects.insert(obj);
		obj->m_mapcell = cell;
		UpdateSet(obj);
	}
}

void MapHandler::RebuildAfterAck(Object* obj)
{
	MapCell* cell = obj->m_mapcell;
	if(cell)
	{
		cell->m_objects.erase(obj);
	}
	AddObject(obj);
}

void MapCell::SearchEnvironment(Object* obj)
{
	i32 a = xid, b = yid;
	for(i32 i = -1; i < 2; ++i)
	{
		for(i32 q = -1; q < 2; ++q)
		{
			MapCell* cell = m_pBelonging->cells[a + i][b + q];
			if(!cell)
				continue;
			cell->FindInRange(obj);
		}
	}
}

void MapHandler::SearchEnvironment(Object* obj, ui32 x, ui32 y)
{
	for(i32 i = -1; i < 2; ++i)
	{
		for(i32 q = -1; q < 2; ++q)
		{
			MapCell* cell = cells[x + i][y + q];
			if(!cell)
				continue;
			cell->FindInRange(obj);
		}
	}
}